<?php

class Upload123Pan
{
    private $api_url = "";
    private $client_id = "";
    private $client_secret = "";

    protected $access_token = "";
    protected $accessTokenKey = "pan123_access_token";
    protected $parentFileIDKey = "pan123_file_parent_id";

    // instance
    private static $instance = null;

    public static function instance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function __construct()
    {
        require_once 'Config.php';
        $config = Config::instance();
        $this->api_url = $config->getApiUrl();
        $this->client_id = $config->getClientID();
        $this->client_secret = $config->getClientSecret();
    }

    // 此接口有访问频率限制。请获取到access_token后本地保存使用，并在access_token过期前及时重新获取。access_token有效期根据返回的expiredAt字段判断。
    public function getAccessToken()
    {
        if($this->access_token){
            return $this->access_token;
        }
        // 是否过期，存储在redis中
        $access_token = $this->getFromRedis($this->accessTokenKey);
        if ($access_token) {
            $this->access_token = $access_token;
            return $access_token;
        }

        $url = $this->api_url . "/api/v1/access_token";

        $data = array(
            'clientID' => $this->client_id,
            'clientSecret' => $this->client_secret,
        );
        // 发送请求
        $response = $this->httpPost($url, $data, $headers);
        // 解析返回结果
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        $data = $result['data'];
        // 获取access_token
        $accessToken = $data['accessToken'];
        $expiredAt = strtotime($data['expiredAt']) - time();
        $this->access_token = $access_token;
        $this->saveToRedis($this->accessTokenKey, $accessToken, $expiredAt);
        return $accessToken;
    }


    


    public function upload(array $files)
    {
        try {
            $full_path = $files['full_path'];

            if (!file_exists($full_path)) {
                throw new \Exception("文件不存在");
            }
        
            // 打开文件进行读取
            $file = fopen($full_path, 'rb');
            if (!$file) {
                throw new \Exception("无法打开文件");
            }
        
            $dirId = $this->getDistributeDirId();
            /**
             * 上传流程
             *   1，请求【创建文件】接口创建文件，接口返回的reuse为true时，表示秒传成功，上传结束。
             *       非秒传的情况将会返回预上传ID preuploadID 与分片大小 sliceSize,请将文件根据分片大小切分。
             *   2，非秒传时，携带步骤1返回的preuploadID 与分片序号sliceNo，请求【获取上传地址接口】获取上传地址。
             *   3，用PUT请求步骤2中返回的地址，上传文件分片。
             *   4，(推荐操作)文件分片上传完成后,调用【列举已上传分片】接口，在本地进行与云端的分片md5比对。注:如果您的文件小于 sliceSize ,该操作将会返回空值,可以不进行这步操作。
             *   5，校验完成后，请求【上传完毕】接口，完成上传。
             *   6，根据步骤5返回的结果，判断是否需要调用【异步轮询获取上传结果】接口，获取上传的最终结果。该时间需要等待，123云盘服务器会校验用户预上传时的MD5与实际上传成功的MD5是否一致。
             */
            $etag = md5_file($full_path);
            $res = $this->createFile($files['name'], $dirId, $files['size'], $etag);
            // reuse
            $fileID = 0;
            if (!$res['reuse']) {
                $preuploadID = $res['preuploadID'];
                $sliceSize = $res['sliceSize'];
                // 总大小 $file['size'] 切分为 $sliceSize 大小的分片
                $totalSlice = ceil($files['size'] / $sliceSize);
                // 上传分片
                $headers = [
                    'Content-Type: application/octet-stream',
                ];
                // // 使用并行处理提高上传效率
                // $promises = [];
                // foreach ($slices as $sliceNo => $sliceData) {
                //     $promises[$sliceNo] = $this->uploadSliceAsync($sliceData);
                // }
                // // 等待所有切片上传完成
                // $results = GuzzleHttp\Promise\settle($promises)->wait();

                for ($sliceNo = 1; $sliceNo <= $totalSlice; $sliceNo++) {
                    $uploadUrl = $this->getUploadUrl($preuploadID, $sliceNo, $sliceSize);
                    // 通过 sliceSize 对 $full_path 文件进行切分
                    fseek($file, ($sliceNo - 1) * $sliceSize);
                    $data = fread($file, $sliceSize);
                    // 用PUT请求步骤2中返回的地址，上传文件分片。
                    $this->uploadFile($uploadUrl, $data,$headers);
                }
                fclose($file);

                // 当文件大小小于 sliceSize 分片大小时,无需调用该接口
                if ($files['size'] > $sliceSize) {
                    // 列举已上传分片 list_upload_parts
                    $uploadMd5 = $this->listUploadParts($preuploadID);
                }

                // 上传完毕
                $uploadRes =  $this->uploadComplete($preuploadID);

                if($uploadRes['async'] == true) {
                    // 异步轮询获取上传结果
                    // 一秒钟请求一次
                    $completed = false;
                    $async_data = [
                        'file_id' => $files['file_id'],
                        'fir_app_id' => $files['fir_app_id'],
                        'preuploadID' => $preuploadID,
                    ];
                    // TODO: 请自行更换对应的轮询服务
                    // Client::send('fir_app_send', $async_data , 60);
                }else if($uploadRes['completed'] == true){
                    $fileID = $uploadRes['fileID'];
                }
            } else {
                $fileID = $res['fileID'];
            }
            $url = '';
            if($fileID > 0){
                // 获取直链
                $directLink = $this->getDirectLink($fileID);
                $url = $directLink['url'];
            }
            return [
                'code' => 0,
                'url'  => $url,
                'fileID'  => $fileID,
                'preuploadID'  => $preuploadID,
            ];
        } catch (\Exception $e) {
            throw new \Exception($e->getMessage());
        }
    }

    // 获取直链
    public function getDirectLink($fileID)
    {
        $url = $this->api_url . "/api/v1/file/get_direct_link";
        $data = array(
            'fileID' => $fileID,
        );
        $response = $this->httpGet($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        $data = $result['data'];
        return $data;
    }

    // listUploadParts
    public function listUploadParts($preuploadID)
    {
        $url = $this->api_url . "/upload/v1/file/list_upload_parts";
        $data = array(
            'preuploadID' => $preuploadID,
        );

        $response = $this->httpPost($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        $data = $result['data'];
        return $data;
    }

    // getUploadUrl
    public function getUploadUrl($preuploadID, $sliceNo, $sliceSize)
    {
        $url = $this->api_url . "/upload/v1/file/get_upload_url";
        $data = [
            'preuploadID' => $preuploadID,
            'sliceNo' => $sliceNo,
        ];

        $response = $this->httpPost($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        $data = $result['data'];
        $uploadUrl = $data['presignedURL'];

        return $uploadUrl;
    }

    // createFile
    protected function createFile($filename, $parentFileId, $size, $etag)
    {
        $url = $this->api_url . "/upload/v1/file/create";
        $data = [
            'filename' => $filename,
            'parentFileID' => $parentFileId,
            'size' => $size,
            'etag' => $etag,
        ];
        $response = $this->httpPost($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        return $result['data'];
    }

    // 异步轮询获取上传结果
    public function getUploadAsyncResult($file_id)
    {
        $url = $this->api_url . "/upload/v1/file/upload_async_result";
        $data = array(
            'preuploadID' => $file_id,
        );

        $response = $this->httpPost($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        var_dump($result);
        return $result;
    }

    // 获取直链目录ID
    protected function getDistributeDirId()
    {
        // 返回 parentFileID 
        $getFromRedis = $this->getFromRedis($this->parentFileIDKey);
        if ($getFromRedis) {
            return $getFromRedis;
        }
        $url = $this->api_url . "/api/v2/file/list";
        $data = array(
            'parentFileId' => 0,
            'limit' => 100,
        );
        // 发送请求
        $response = $this->httpGet($url, $data, $headers);
        // 解析返回结果
        $result = json_decode($response, true);

        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        $fileList = $result['data']['fileList'];

        // 查找是否有 category = 0 并且 parentFileId = 0 并且 filename 为 distribute 的文件夹
        $distributeFolder = null;
        foreach ($fileList as $file) {
            if ($file['category'] == 0 && $file['parentFileId'] == 0 && $file['filename'] == 'distribute') {
                $distributeFolder = $file;
                break;
            }
        }

        if (!$distributeFolder) {
            // 创建 distribute 文件夹
            return $this->createFolder('distribute');
        } else {
            $this->saveToRedis($this->parentFileIDKey, $distributeFolder['fileId']);
        }

        return $distributeFolder['fileId'];

        // DirectLink
    }

    // createFolder
    protected function createFolder($folderName)
    {
        $url = $this->api_url . "/upload/v1/file/mkdir";
        $data = array(
            'parentID' => 0,
            'name' => $folderName,
        );
        $headers = array(
            'Platform: open_platform',
            'Content-Type: application/json',
            'Authorization: Bearer ' . $access_token,
        );

        $response = $this->httpPost($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        // 启用直链功能
        $this->enableDirectLink($result['data']['dirID']);

        return $result['data']['dirID'];
    }

    // enableDirectLink
    protected function enableDirectLink($fileID)
    {
        $url = $this->api_url . "/api/v1/direct-link/enable";
        $data = array(
            'fileID' => $fileID,
        );
        $response = $this->httpPost($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }

        $this->saveToRedis($this->parentFileIDKey, $fileID);
        return $result;
    }

   

    // uploadComplete
    protected function uploadComplete($preuploadID)
    {
        $url = $this->api_url . "/upload/v1/file/upload_complete";
        $data = [
            'preuploadID' => $preuploadID,
        ];
        $response = $this->httpPost($url, $data, $headers);
        $result = json_decode($response, true);
        if ($result['code'] != 0) {
            // 抛出异常
            throw new \Exception($result['message']);
        }
        return $result['data'];
    }

     protected function getFromRedis($key)
    {
        // TODO：请自行更换redis缓存
        // TODO: 从redis中获取access_token
        // $access_token = Redis::get($key);
        // if (!$access_token) {
        //    return false;
        // }
        // return $access_token;
    }

    protected function saveToRedis($key, $value, $expiredAt = 0)
    {
        // TODO：请自行更换redis缓存
        // TODO: 保存access_token到redis
        // Redis::set($key, $value);
        // if ($expiredAt > 0) {
        //    Redis::expire($key, $expiredAt);
        // }
    }

    // uploadFile put 请求上传文件
    protected function uploadFile($uploadUrl, $data, $headers = [
    ])
    {
        
        $tempFilePath = tempnam(sys_get_temp_dir(), 'upload_chunk_');
        file_put_contents($tempFilePath, $data);
        $tempHandle = fopen($tempFilePath, 'rb');
        // 获取临时文件的大小
        $tempFileSize = filesize($tempFilePath);
        $headers[] = 'Content-Length: '. $tempFileSize;
        // 发送请求
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $uploadUrl);
        curl_setopt($ch, CURLOPT_PUT, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER,  $headers);
        curl_setopt($ch, CURLOPT_INFILE, $tempHandle);
        curl_setopt($ch, CURLOPT_INFILESIZE, strlen($data));
    
        $response = curl_exec($ch);
        fclose($tempHandle);
        curl_close($ch);
        unlink($tempFilePath);
        return json_decode($response, true);
    }


    protected function httpPost($url, $data, $headers)
    {

        $defaultHeaders = [
            'Platform: open_platform',
            'Content-Type: application/json',
        ];

        $access_token = $this->getAccessToken();
        if ($access_token) {
            $defaultHeaders[] = 'Authorization: Bearer ' . $access_token;
        }

        $headers = array_merge($defaultHeaders, $headers);

        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($data),
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false, // 启用SSL证书验证
        ]);

        $response = curl_exec($curl);
        if (curl_errno($curl)) {
            throw new \Exception(curl_error($curl));
        }
        curl_close($curl);
        return $response;

    }

    protected function httpGet($url, $data, $headers)
    {

        $defaultHeaders = [
            'Platform: open_platform',
            'Content-Type: application/json',
        ];

        $access_token = $this->getAccessToken();
        if ($access_token) {
            $defaultHeaders[] = 'Authorization: Bearer ' . $access_token;
        }

        $headers = array_merge($defaultHeaders, $headers);

        $curl = curl_init();
        curl_setopt_array($curl, [
            CURLOPT_URL => $url . '?' . http_build_query($data),
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false, // 启用SSL证书验证
        ]);

        $response = curl_exec($curl);
        if (curl_errno($curl)) {
            throw new \Exception(curl_error($curl));
        }
        curl_close($curl);
        return $response;
    }
}
